// ***********************************************************
// Android AlertDialog allowing selection of directory and and or filename.
//
// Copyright �   2014 Craig Greenock.
// Version 0.0 - Jan 2014
// Machine     - KEFALLONIA Microsoft Windows NT 6.1.7600.0
// Contact     - cgreenock@bcs.org.uk
// 
// ***********************************************************
package com.aqi00.lib.dialog;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import com.aqi00.lib.R;
import com.aqi00.lib.util.FileResource;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

/**
 * Select a directory or a file.
 * 
 * */
public class FileSelectFragment extends DialogFragment implements
		OnItemClickListener, OnItemLongClickListener {
	private static final String TAG = "FileSelectFragment";

	/*
	 * Use the unicode "back" triangle to indicate there is a parent directory
	 * rather than an icon to minimise file dependencies.
	 * 
	 * You may have to find an alternative symbol if the font in use doesn't
	 * support this character.
	 */
	final String PARENT = "\u25C0";

	private FileSelectCallbacks mCallbacks;
	private ArrayList<File> fileList;

	// The widgets required to provide the UI.
	private TextView selectedPath;
	private TextView selectedFile;
	private LinearLayout root;
	private ListView directoryView;

	// The directory the user has selected.
	private File currentDirectory;
	private File currentFile;

	// Resource IDs
	private int resourceID_OK;
	private int resourceID_Cancel;
	private int resourceID_Title;
	private int resourceID_Icon;
	private int resourceID_Dir;
	private int resourceID_UpDir;
	private int resourceID_File;

	private int dialog_Height;
	private Map<String, Object> mapParam;

	// How the popup is to be used.
	private Mode selectionMode;

	// Filtered view of the directories.
	private FilenameFilter fileFilter;

	/** How do we want to use the selector? */
	public enum Mode {
		DirectorySelector, FileSelector
	}

	/**
	 * Signal to / request action of host activity.
	 * 
	 * */
	public interface FileSelectCallbacks {

		/**
		 * Hand selected path and name to context for use. If user cancels
		 * absolutePath and filename are handed out as null.
		 * 
		 * @param absolutePath
		 *            - Absolute path to target directory.
		 * @param fileName
		 *            - Filename. Will be null if Mode = DirectorySelector
		 * 
		 * */
		public void onConfirmSelect(String absolutePath, String fileName, Map<String, Object> map_param);

		/**
		 * Allow the client activity to check file content / format whilst the
		 * user still has the popup in view.
		 * 
		 * The alternative is to provide a custom filter that examines file
		 * content as it goes, but that could get very slow very quickly
		 * especially for binary files.
		 * 
		 * */
		public boolean isFileValid(String absolutePath, String fileName, Map<String, Object> map_param);

	}

	/**
	 * Provide a standard filter to match any file with an extension in the
	 * supplied list. Case insensitive.. Directories are always accepted.
	 * 
	 * @param fileExtensions
	 *            List of file extensions including full stop. .xml, .txt etc.
	 * */
	public static FilenameFilter FiletypeFilter(
			final ArrayList<String> fileExtensions) {
		
		FilenameFilter fileNameFilter = new FilenameFilter() {

			@Override
			public boolean accept(File directory, String fileName) {

				boolean matched = false;
				File f = new File(String.format("%s/%s",
						directory.getAbsolutePath(), fileName));

				// We let all directories through.
				matched = f.isDirectory();

				if (!matched) {
					for (String s : fileExtensions) {
						s = String.format(".{0,}\\%s$", s);
						s = s.toUpperCase(Locale.getDefault());
						fileName = fileName.toUpperCase(Locale.getDefault());
						matched = fileName.matches(s);
						if (matched) {
							break;
						}
					}
				}

				return matched;

			}
		};

		return fileNameFilter;

	}

	/**
	 * Create new instance of a file save popup.
	 * 
	 * @param Mode
	 *            - Directory selector or File selector?
	 * @param resourceID_OK
	 *            - String resource ID for the positive (OK) button.
	 * @param resourceID_Cancel
	 *            - String resource ID for the negative (Cancel) button.
	 * @param resourceID_Title
	 *            - String resource ID for the dialogue's title.
	 * @param resourceID_Icon
	 *            - Drawable resource ID for the dialogue's title bar icon.
	 * @param resourceID_Directory
	 *            - Drawable resource ID for a directory icon. Distinguish dirs
	 *            from files.
	 * @param resourceID_File
	 *            - Drawable resource ID for a file icon.
	 * */
	public static FileSelectFragment newInstance(Mode selectionMode,
			int resource_DialogHeight, int resourceID_OK,
			int resourceID_Cancel, int resourceID_Title, int resourceID_Icon,
			int resourceID_Directory, int resourceID_UpDirectory,
			int resourceID_File) {

		FileSelectFragment frag = new FileSelectFragment();
		Bundle args = new Bundle();
		args.putInt("mode", selectionMode.ordinal());
		args.putInt("dialogHeight", resource_DialogHeight);
		args.putInt("captionOK", resourceID_OK);
		args.putInt("captionCancel", resourceID_Cancel);
		args.putInt("popupTitle", resourceID_Title);
		args.putInt("iconPopup", resourceID_Icon);
		args.putInt("iconDirectory", resourceID_Directory);
		args.putInt("iconUpDirectory", resourceID_UpDirectory);
		args.putInt("iconFile", resourceID_File);
		frag.setArguments(args);
		return frag;
	}

	/**
	 * Optional. Allow restriction of file names/types displayed for selection.
	 * 
	 * @param fileFilter
	 *            - May be null. Custom rule for selecting directories/files.
	 * 
	 * */
	public void setFilter(FilenameFilter fileFilter, Map<String, Object> map_param) {
		mapParam = map_param;
		this.fileFilter = fileFilter;
	}

	/**
	 * Note the parent activity for callback purposes.
	 * 
	 * @param activity
	 *            - parent activity
	 * */
	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		// The containing activity is expected to implement the fragment's
		// callbacks otherwise it can't react to item changes.
		if (!(activity instanceof FileSelectCallbacks)) {
			throw new IllegalStateException(
					"Activity must implement fragment's callbacks.");
		}

		mCallbacks = (FileSelectCallbacks) activity;
		fileList = new ArrayList<File>();
		dialog_Height = getArguments().getInt("dialogHeight");
		resourceID_OK = getArguments().getInt("captionOK");
		resourceID_Cancel = getArguments().getInt("captionCancel");
		resourceID_Title = getArguments().getInt("popupTitle");
		resourceID_Icon = getArguments().getInt("iconPopup");
		resourceID_File = getArguments().getInt("iconFile");
		resourceID_Dir = getArguments().getInt("iconDirectory");
		resourceID_UpDir = getArguments().getInt("iconUpDirectory");
		selectionMode = Mode.values()[getArguments().getInt("mode")];

	}

	/**
	 * Build the popup.
	 * */
	@Override
	public Dialog onCreateDialog(Bundle savedInstanceState) {

		/*
		 * Use the same callback for [OK] & [Cancel]. Hand out nulls to indicate
		 * abandonment.
		 */

		/*
		 * We want to make this a transportable piece of code so don't want an
		 * XML layout dependency so layout is set up in code.
		 * 
		 * [ListView of directory & file names ] [ ] [ ] [ ]
		 * ------------------------------------------------------ {current
		 * path}/ {selected file}
		 */

		// Set up the container view.
		LinearLayout.LayoutParams rootLayout = new LinearLayout.LayoutParams(
				ViewGroup.LayoutParams.MATCH_PARENT,
				ViewGroup.LayoutParams.WRAP_CONTENT, 0.0F);
		root = new LinearLayout(getActivity());
		root.setOrientation(LinearLayout.VERTICAL);
		root.setLayoutParams(rootLayout);

		/*
		 * Set up initial sub-directory list.
		 */
		currentDirectory = new File(Environment.getExternalStorageDirectory().toString()+"/Download/");
		fileList = getDirectoryContent(currentDirectory);
		DirectoryDisplay displayFormat = new DirectoryDisplay(getActivity(),
				fileList);

		/*
		 * Fix the height of the listview at 150px, enough to show 3 or 4
		 * entries at a time. Don't want the popup shrinking and growing all the
		 * time. Tried it. Most disconcerting.
		 */
		LinearLayout.LayoutParams listViewLayout = new LinearLayout.LayoutParams(
				ViewGroup.LayoutParams.MATCH_PARENT, dialog_Height, 0.0F);
		directoryView = new ListView(getActivity());
		directoryView.setLayoutParams(listViewLayout);
		directoryView.setAdapter(displayFormat);
		/*
		 * Click on file or directory - select Long click on directory - open
		 * directory
		 */
		directoryView.setOnItemClickListener(this);
		directoryView.setOnItemLongClickListener(this);
		root.addView(directoryView);

		View horizDivider = new View(getActivity());
		horizDivider.setBackgroundColor(Color.CYAN);
		root.addView(horizDivider, new ViewGroup.LayoutParams(
				ViewGroup.LayoutParams.MATCH_PARENT, 2));

		/*
		 * Now set up the filename display area.
		 * 
		 * {current path}/ {selected file}
		 */
		LinearLayout nameArea = new LinearLayout(getActivity());
		nameArea.setOrientation(LinearLayout.HORIZONTAL);
		nameArea.setLayoutParams(rootLayout);
		root.addView(nameArea);

		selectedPath = new TextView(getActivity());
		selectedPath.setText(currentDirectory.getAbsolutePath() + "/");
		nameArea.addView(selectedPath);

		// We only display a selected filename in FileSelector mode.
		if (selectionMode == Mode.FileSelector) {
			selectedFile = new TextView(getActivity());
			selectedFile.setGravity(Gravity.LEFT);
			selectedFile.setPadding(2, 0, 6, 0);
			nameArea.addView(selectedFile);
		}

		// Use the standard AlertDialog builder to create the popup.
		// Avoid call chaining, keep the code readable and maintainable.
		Builder popupBuilder = new AlertDialog.Builder(getActivity());
		//Builder popupBuilder = new AlertDialog.Builder(new ContextThemeWrapper(
		//		this, R.style.AlertDialogCustom));
		popupBuilder.setView(root);
		popupBuilder.setIcon(resourceID_Icon);
		popupBuilder.setTitle(resourceID_Title);

		// Set up anonymous methods to handle [OK] & [Cancel] clicks.
		popupBuilder.setPositiveButton(resourceID_OK,
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int whichButton) {
						// Empty method. See onStart.
					}
				});

		popupBuilder.setNegativeButton(resourceID_Cancel,
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int whichButton) {
					}
				});

		return popupBuilder.create();
	}

	/**
	 * Provide the [PositiveButton] with a click listener that doesn't dismiss
	 * the popup if the user has chosen a file (or directory) that is
	 * unsuitable.
	 * 
	 * */
	@Override
	public void onStart() {
		super.onStart();
		AlertDialog d = (AlertDialog) getDialog();
		if (d != null) {
			Button positiveButton = (Button) d
					.getButton(Dialog.BUTTON_POSITIVE);
			positiveButton.setOnClickListener(new View.OnClickListener() {
				@Override
				public void onClick(View v) {

					String absolutePath = currentDirectory.getAbsolutePath();
					String filename = null;
					if (currentFile != null) {
						filename = currentFile.getName();
					}

					if (mCallbacks.isFileValid(absolutePath, filename, mapParam)) {
						dismiss();
						mCallbacks.onConfirmSelect(absolutePath, filename, mapParam);
					}
				}
			});
		}
	}

	/**
	 * Single short click/press selects a file.
	 * */
	@Override
	public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long id) {

		currentFile = null;

		String file_path = currentDirectory.getAbsolutePath();
		if (currentDirectory.getParent() != null) {
			file_path += "/";
		}
		selectedPath.setText(file_path);

		if (pos >= 0 || pos < fileList.size()) {
			currentFile = fileList.get(pos);

			String file_name = currentFile.getName();

			if (!currentFile.isDirectory() && !file_name.equals(PARENT)
					&& selectionMode == Mode.FileSelector) {
				selectedFile.setText(currentFile.getName());
			}

		}
		
		

		File selected = null;

		if (pos >= 0 || pos < fileList.size()) {
			selected = fileList.get(pos);
			String name = selected.getName();

			if (selected.isDirectory() || name.equals(PARENT)) {

				// Are we going up or down?
				if (name.equals(PARENT)) {
					currentDirectory = currentDirectory.getParentFile();
				} else {
					currentDirectory = selected;
				}

				// Refresh the listview display for the newly selected
				// directory.
				fileList = getDirectoryContent(currentDirectory);
				DirectoryDisplay displayFormatter = new DirectoryDisplay(
						getActivity(), fileList);
				directoryView.setAdapter(displayFormatter);

				// Update the path TextView widgets. Tell the user where he or
				// she is and clear the selected file.
				currentFile = null;
				String path = currentDirectory.getAbsolutePath();
				if (currentDirectory.getParent() != null) {
					path += "/";
				}

				selectedPath.setText(path);
				if (selectionMode == Mode.FileSelector) {
					selectedFile.setText(null);
				}

			}

		}

	}

	/**
	 * Long click/press on a directory selects and opens that directory.
	 * */
	@Override
	public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int pos,
			long id) {

		File selected = null;

		if (pos >= 0 || pos < fileList.size()) {
			selected = fileList.get(pos);
			String name = selected.getName();

			if (selected.isDirectory() || name.equals(PARENT)) {

				// Are we going up or down?
				if (name.equals(PARENT)) {
					currentDirectory = currentDirectory.getParentFile();
				} else {
					currentDirectory = selected;
				}

				// Refresh the listview display for the newly selected
				// directory.
				fileList = getDirectoryContent(currentDirectory);
				DirectoryDisplay displayFormatter = new DirectoryDisplay(
						getActivity(), fileList);
				directoryView.setAdapter(displayFormatter);

				// Update the path TextView widgets. Tell the user where he or
				// she is and clear the selected file.
				currentFile = null;
				String path = currentDirectory.getAbsolutePath();
				if (currentDirectory.getParent() != null) {
					path += "/";
				}

				selectedPath.setText(path);
				if (selectionMode == Mode.FileSelector) {
					selectedFile.setText(null);
				}

			}

		}

		return false;
	}

	/**
	 * Identify all sub-directories and files within a directory.
	 * 
	 * @param directory
	 *            The directory to walk.
	 * */
	private ArrayList<File> getDirectoryContent(File directory) {

		ArrayList<File> displayedContent = new ArrayList<File>();
		File[] files = null;

		if (fileFilter != null) {
			files = directory.listFiles(fileFilter);
		} else {
			files = directory.listFiles();
		}

		// Allow navigation back up the tree when the directory is a
		// sub-directory.
		if (directory.getParent() != null) {
			displayedContent.add(new File(PARENT));
		}

		// Get the content in this directory.
		if (files != null) {
			for (File f : files) {

				boolean canDisplay = true;

				if (selectionMode == Mode.DirectorySelector && !f.isDirectory()) {
					canDisplay = false;
				}

				canDisplay = (canDisplay && !f.isHidden());

				if (canDisplay) {
					displayedContent.add(f);
				}
			}
		}

		return displayedContent;

	}

	/**
	 * Display the sub-directories in a selected directory.
	 * 
	 * */
	private class DirectoryDisplay extends ArrayAdapter<File> {

		public DirectoryDisplay(Context context, List<File> displayContent) {
			super(context, android.R.layout.simple_list_item_1, displayContent);
		}

		/**
		 * Display the name of each sub-directory.
		 * */
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {

			int iconID = resourceID_File;
			// We assume that we've got a parent directory...
			TextView textview = (TextView) super.getView(position, convertView,
					parent);

			// If we've got a directory then get its name.
			// If it's a file we show the file icon, if a directory then the
			// directory icon.
			if (fileList.get(position) != null) {
				String name = fileList.get(position).getName();
				textview.setText(name);

				if (fileList.get(position).isDirectory()) {
					iconID = resourceID_Dir;
				}

				if (name.equals(PARENT)) {
					// iconID = -1;
					iconID = resourceID_UpDir;
				}

				// Icon to the left of the text.
				if (iconID > 0) {
					Drawable icon = getActivity().getResources().getDrawable(
							iconID);
					textview.setCompoundDrawablesWithIntrinsicBounds(icon,
							null, null, null);
				}

			}

			return textview;
		}

	}

	public static void show(Context context, String[] extensions, Map<String, Object> param) {
		Activity act = (Activity) context;
		FileResource fileRes = new FileResource(act);
		FileSelectFragment fsf = FileSelectFragment.newInstance(
				FileSelectFragment.Mode.FileSelector,
				fileRes.dialog_height, // 对话框高度
				R.string.btn_ok, R.string.btn_cancel,
				R.string.tag_title_OpenFile, fileRes.resourceID_Icon,
				fileRes.resourceID_Directory, fileRes.resourceID_UpDirectory,
				fileRes.resourceID_File);
		ArrayList<String> allowedExtensions = new ArrayList<String>();
		for (int i=0; i<extensions.length; i++) {
			allowedExtensions.add("."+extensions[i]);
			allowedExtensions.add("."+extensions[i].toUpperCase(Locale.getDefault()));
		}
		fsf.setFilter(FileSelectFragment.FiletypeFilter(allowedExtensions), param);

		String fragTag = act.getResources().getString(R.string.tag_fragment_FileSelect);
		fsf.show(act.getFragmentManager(), fragTag);
	}

}
